﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.IO;
using System.Text;
using OpenQuarters.WebQuarters.Utils;
using System.Web.Mvc;
using System.Text.RegularExpressions;
using HtmlAgilityPack;

namespace OpenQuarters.WebQuarters.Core
{
    [Serializable]
    public class DefaultJFormClass
    {
    }

    public class JForm : JFormControl<DefaultJFormClass>
    {
        public JForm(string FormTemplate, string PostUrl) : base(FormTemplate, PostUrl)
        {
        }

        public JForm(string FormTemplate, string PostUrl, object HiddenFields)
            : base(FormTemplate, PostUrl, HiddenFields)
        {
        }

        public JForm(string FormTemplate, TemplateType formTemplateType, string PostUrl)
            : base(FormTemplate, formTemplateType, PostUrl)
        {
        }

        public JForm(string FormTemplate, TemplateType formTemplateType, string PostUrl, object HiddenFields)
            : base(FormTemplate, formTemplateType, PostUrl, HiddenFields)
        {
        }

        public JForm(string FormTemplate, string PostUrl, string NoScriptPostUrl)
            : base(FormTemplate, PostUrl, NoScriptPostUrl)
        {
        }

        public JForm(string FormTemplate, string PostUrl, string NoScriptPostUrl, object HiddenFields)
            : base(FormTemplate, PostUrl, NoScriptPostUrl, HiddenFields)
        {
        }

        public JForm(string FormTemplate, TemplateType formTemplateType, string PostUrl, string NoScriptPostUrl)
            : base(FormTemplate, formTemplateType, PostUrl, NoScriptPostUrl)
        {
        }

        public JForm(string FormTemplate, TemplateType formTemplateType, string PostUrl, string NoScriptPostUrl, object HiddenFields)
            : base(FormTemplate, formTemplateType, PostUrl, NoScriptPostUrl, HiddenFields)
        {
        }

        public JForm()
            : base()
        {
        }
    }

    public class JFormControl<T> : ViewUserControl where T : class
    {
        public static void AddError(string key, string errorMessage)
        {
            if (JFormResponse<T>.Current == null)
            {
                JFormResponse<T>.Current = new JFormResponse<T>();
            }
            JFormResponse<T>.Current.errorMessages[key].Add(errorMessage);
        }

        public List<string> GetErrors()
        {
            if (JFormResponse<T>.Current != null)
            {
                List<string> errors = new List<string>();
                foreach (string key in JFormResponse<T>.Current.errorMessages.Keys)
                {
                    foreach (string error in JFormResponse<T>.Current.errorMessages[key])
                    {
                        errors.Add(error);
                    }
                }
                return errors;
            }
            return new List<string>();
        }

        public List<string> GetMessages()
        {
            if (JFormResponse<T>.Current != null)
            {
                List<string> messages = new List<string>();
                foreach (string key in JFormResponse<T>.Current.messages.Keys)
                {
                    foreach (string message in JFormResponse<T>.Current.messages[key])
                    {
                        messages.Add(message);
                    }
                }
                return messages;
            }
            return new List<string>();
        }

        public List<string> GetErrors(string key)
        {
            if (JFormResponse<T>.Current != null)
            {
                if (JFormResponse<T>.Current.errorMessages.Keys.Contains(key))
                    return JFormResponse<T>.Current.errorMessages[key];
                else
                return null;
            }
            return new List<string>();
        }


        public List<string> GetMessagess(string key)
        {
            if (JFormResponse<T>.Current != null)
            {
                if (JFormResponse<T>.Current.messages.Keys.Contains(key))
                    return JFormResponse<T>.Current.messages[key];
                else
                    return null;
            }
            return new List<string>();
        }


        public enum TemplateType
        {
            File,
            EmbeddedResource
        }

        public bool clearResponse = true;

        public bool ClearResponse
        {
            get { return clearResponse; }
            set { clearResponse = value; }
        }

        private TemplateType formTemplateType = TemplateType.File;

        public TemplateType FormTemplateType
        {
            get { return formTemplateType; }
            set { formTemplateType = value; }
        }

        private string formTemplate = "";

        public string FormTemplate
        {
            get { return formTemplate; }
            set { formTemplate = value; }
        }


        public enum MethodType
        {
            POST,
            GET
        }

        private MethodType method = MethodType.POST;

        public MethodType Method
        {
            get { return method; }
            set { method = value; }
        }
        
        private string actionUrl = "/";

        public string ActionUrl
        {
            get { return actionUrl; }
            set { actionUrl = value; }
        }

        private string noScriptActionUrl = "";

        public string NoScriptActionUrl
        {
            get { return (string.IsNullOrEmpty(noScriptActionUrl) ? actionUrl : noScriptActionUrl); }
            set { noScriptActionUrl = value; }
        }

        private string onLoadedScript = "";

        public string OnLoadedScript
        {
            get { return onLoadedScript; }
            set { onLoadedScript = value; }
        }

        private string onProceedScript = "";

        public string OnProceedScript
        {
            get { return onProceedScript; }
            set { onProceedScript = value; }
        }

        private string onSuccessScript = "";

        public string OnSuccessScript
        {
            get { return onSuccessScript; }
            set { onSuccessScript = value; }
        }

        private bool useJavaScript = true;

        public bool UseJavaScript
        {
            get { return useJavaScript; }
            set { useJavaScript = value; }
        }

        private string containerId = null;

        public string ContainerId
        {
            get
            {
                if (containerId == null)
                {
                    containerId = Guid.NewGuid().ToString().Replace("-", "_");
                }
                return containerId;
            }
            set { containerId = value; }
        }

        private object hiddenInputs = null;

        public object HiddenInputs
        {
            get { return hiddenInputs; }
            set { hiddenInputs = value; }
        }

        public JFormControl()
        {
        }

        public JFormControl(string FormTemplate, string PostUrl)
        {
            this.FormTemplate = FormTemplate;
            this.ActionUrl = PostUrl;
        }

        public JFormControl(string FormTemplate, string PostUrl, object HiddenFields)
        {
            this.FormTemplate = FormTemplate;
            this.ActionUrl = PostUrl;
            this.hiddenInputs = HiddenFields;
        }

        public JFormControl(string FormTemplate, TemplateType formTemplateType, string PostUrl)
        {
            this.FormTemplate = FormTemplate;
            this.FormTemplateType = formTemplateType;
            this.ActionUrl = PostUrl;
        }

        public JFormControl(string FormTemplate, TemplateType formTemplateType, string PostUrl, object HiddenFields)
        {
            this.FormTemplate = FormTemplate;
            this.FormTemplateType = formTemplateType;
            this.ActionUrl = PostUrl;
            this.hiddenInputs = HiddenFields;
        }

        public JFormControl(string FormTemplate, string PostUrl, string NoScriptPostUrl)
        {
            this.FormTemplate = FormTemplate;
            this.ActionUrl = PostUrl;
            this.NoScriptActionUrl = NoScriptPostUrl;
        }

        public JFormControl(string FormTemplate, string PostUrl, string NoScriptPostUrl, object HiddenFields)
        {
            this.FormTemplate = FormTemplate;
            this.ActionUrl = PostUrl;
            this.NoScriptActionUrl = NoScriptPostUrl;
            this.hiddenInputs = HiddenFields;
        }

        public JFormControl(string FormTemplate, TemplateType formTemplateType, string PostUrl, string NoScriptPostUrl)
        {
            this.FormTemplate = FormTemplate;
            this.FormTemplateType = formTemplateType;
            this.ActionUrl = PostUrl;
            this.NoScriptActionUrl = NoScriptPostUrl;
        }

        public JFormControl(string FormTemplate, TemplateType formTemplateType, string PostUrl, string NoScriptPostUrl, object HiddenFields)
        {
            this.FormTemplate = FormTemplate;
            this.FormTemplateType = formTemplateType;
            this.ActionUrl = PostUrl;
            this.NoScriptActionUrl = NoScriptPostUrl;
            this.hiddenInputs = HiddenFields;
        }

        public string GetNoScriptForm()
        {
            string formContents = "";

            switch (FormTemplateType)
            {
                case TemplateType.EmbeddedResource:
                    string[] pathArray = FormTemplate.Split(',');
                    if (pathArray.Length != 2)
                    {
                        throw new ArgumentException("The embedded form path was not valid");
                    }
                    formContents = new StreamReader(Resource.GetEmbeddedFile(pathArray[0], pathArray[1])).ReadToEnd();
                    break;
                default:
                    formContents = File.ReadAllText(Oracle.Context.Server.MapPath("~/Forms/") + FormTemplate + ".tpl");
                    break;
            }

            //Do Translation Replacements
            List<string> requiredTranslations = new List<string>();
            var translationMatches = Regex.Matches(formContents, @"{\$T\.(.[^\}]*)}", RegexOptions.Multiline);
            foreach (Match translationMatch in translationMatches)
            {
                if (!requiredTranslations.Contains(translationMatch.Value))
                    requiredTranslations.Add(translationMatch.Value.Substring(4, translationMatch.Value.Length - 5));
            }

            foreach (var translation in Translation.Get(requiredTranslations))
            {
                formContents = formContents.Replace("{$T." + translation.Key + "}", translation.Value);
            }

           
            //Do dropper replacements
            var selectMatches = Regex.Matches(formContents, @"<select.*rel=""(.*)"".*>", RegexOptions.Multiline);
            Dictionary<string, Dictionary<string, string>> replacementSelects = new Dictionary<string, Dictionary<string, string>>();
            foreach (Match match in selectMatches)
            {
                if (!replacementSelects.ContainsKey(match.Value))
                    replacementSelects.Add(match.Value, Resources.GetOptionListDictionary(match.Groups[1].Value));
            }
            foreach (KeyValuePair<string, Dictionary<string, string>> replacement in replacementSelects)
            {
                if (replacement.Key.Substring(replacement.Key.Length - 2, 1) == "/")
                    formContents = formContents.Replace(replacement.Key, replacement.Key + GetOptionList(replacement.Value) + "</select>");
                else
                    formContents = formContents.Replace(replacement.Key, replacement.Key + GetOptionList(replacement.Value));
            }

            //Parse param fields using HtmlAgilityPack and Linq
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(formContents);
            var xdoc = doc.ToXDocument();

            var formParams = from el in xdoc.Descendants()
                             where (el.Name.LocalName.ToLower() == "input" ||
                                    el.Name.LocalName.ToLower() == "select" ||
                                    el.Name.LocalName.ToLower() == "textarea") &&
                                    (el.Attribute("class") != null && el.Attribute("class").Value.Contains("param"))
                             select new { el, };

            //Repopulate form
            if (formParams != null && JFormResponse<T>.Current != null && JFormResponse<T>.Current.FormParams != null)
            {
                foreach (var element in formParams)
                {
                    if (element.el.Attribute("name") != null)
                    {
                        string name = element.el.Attribute("name").Value;
                        if (JFormResponse<T>.Current.FormParams[name] != null)
                        {
                            switch (element.el.Name.LocalName)
                            {
                                case "select":
                                    //Reselect property
                                    try
                                    {
                                        var options = element.el.Descendants().Where(i => i.Name.LocalName == "option");
                                        options.Where(o => o.Attribute("value").Value == JFormResponse<T>.Current.FormParams[name]).ToList().ForEach(o => o.SetAttributeValue("selected", "selected"));
                                    }
                                    catch (NullReferenceException) { }
                                    break;
                                case "textarea":
                                    element.el.SetValue(JFormResponse<T>.Current.FormParams[name]);
                                    break;
                                default:
                                    element.el.SetAttributeValue("value", JFormResponse<T>.Current.FormParams[name]);
                                    break;
                            }
                        }
                    }
                }
            }
            
           
            StringBuilder sb = new StringBuilder();
            xdoc.Save(new System.IO.StringWriter(sb));

            //Add any injected hidden inputs
            Dictionary<string, object> hidden = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            if (this.hiddenInputs != null)
            {
                foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(this.hiddenInputs))
                {
                    object obj2 = descriptor.GetValue(this.hiddenInputs);
                    hidden.Add(descriptor.Name, obj2);
                }

                foreach (KeyValuePair<string, object> field in hidden)
                {
                    sb.AppendLine(string.Format("<input type=\"hidden\" id=\"{0}\" name=\"{0}\" value=\"{1}\" />", field.Key, field.Value.ToString()));
                }
            }
            
            formContents = sb.ToString().Replace("&amp;", "&").Replace("<?xml version=\"1.0\" encoding=\"utf-16\"?>", "");

            //Do validation replacements
            var validationMatches = Regex.Matches(formContents, @"<(div|span).*class=""validationMessage"".*rel=""(.*)"".*(/>|></(div|span)>)", RegexOptions.Multiline);
            Dictionary<string, string> replacementValidations = new Dictionary<string, string>();
            foreach (Match match in validationMatches)
            {
                if (!replacementValidations.ContainsKey(match.Value))
                    replacementValidations.Add(match.Value, match.Groups[1].Value);
            }
            foreach (KeyValuePair<string, string> replacement in replacementValidations)
            {
                List<string> errorMessages = GetErrors(replacement.Value);

                if (errorMessages != null && errorMessages.Count != 0)
                {
                    var errorMessagesConcat = new StringBuilder();
                    errorMessages.ForEach(i => errorMessagesConcat.Append(i + "; "));
                    formContents = formContents.Replace(replacement.Key, "<span class=\"field-validation-error\">" + errorMessagesConcat.ToString() + "</span>");
                    
                }
                else
                {
                    formContents = formContents.Replace(replacement.Key, "");
                }
            }

            var validationSummaryMatches = Regex.Matches(formContents, @"<(div|span).*class=""validationSummary"".*(/>|></(div|span)>)", RegexOptions.Multiline);
            if (validationSummaryMatches.Count != 0)
            {

                StringBuilder validationSummary = new StringBuilder();
                validationSummary.AppendLine("<ul class=\"validationSummary\">");

                foreach (string error in GetErrors())
                {
                    validationSummary.AppendLine(string.Format("<li>{0}</li>", error));
                }
                validationSummary.AppendLine("</ul>");

                foreach (Match match in validationSummaryMatches)
                {
                    formContents = formContents.Replace(match.Value, validationSummary.ToString());
                }
            }

            var messageSummaryMatches = Regex.Matches(formContents, @"<(div|span).*class=""messageSummary"".*(/>|></(div|span)>)", RegexOptions.Multiline);
            if (messageSummaryMatches.Count != 0)
            {

                StringBuilder messageSummary = new StringBuilder();
                messageSummary.AppendLine("<ul class=\"messageSummary\">");

                foreach (string error in GetMessages())
                {
                    messageSummary.AppendLine(string.Format("<li>{0}</li>", error));
                }
                messageSummary.AppendLine("</ul>");

                foreach (Match match in messageSummaryMatches)
                {
                    formContents = formContents.Replace(match.Value, messageSummary.ToString());
                }
            }

            return @"<form method=""" + this.Method.ToString() + @""" action=""" + this.NoScriptActionUrl + @""">
            <input type=""hidden"" id=""cmsEnv_NoScript"" name=""cmsEnv_NoScript"" value=""true"" />
" + formContents + @"
        </form>";
        }

        public string GetOptionList(Dictionary<string, string> list)
        {
            StringBuilder sb = new StringBuilder();
            foreach (KeyValuePair<string, string> item in list)
            {
                sb.AppendLine("<option value=\"" + item.Key + "\">" + item.Value + "</option>");
            }
            return sb.ToString();
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            if (UseJavaScript)
            {
                this.Controls.AddLiteral(@"
<div id=""" + ContainerId + @""">
    <noscript>
        " + GetNoScriptForm() + @"
    </noscript>
</div>
");

                string script = @"$('#" + ContainerId + @"').jForm({
    form: '" + FormTemplate + @"',
    hiddenFields: " + this.hiddenInputs.ToJSON() + @",
    method: '" + Method + @"',
    url: '" + ActionUrl + @"'" + (OnLoadedScript.Length > 0 ? @",
    onLoaded: function(response) {
        " + OnLoadedScript + @"
    }" : "") + (OnProceedScript.Length > 0 ? @",
    onProceed: function(response) {
        " + OnProceedScript + @"
    }" : "") + (OnSuccessScript.Length > 0 ? @",
    onSuccess: function(response) {
        " + OnSuccessScript + @"
    }" : "") + @",
    hiddenFields: " + this.hiddenInputs.ToJSON() + @",
    ajaxMode: " + UseJavaScript.ToString().ToLower() + @"
});";

                OpenQuarters.WebQuarters.Core.JavaScript.RegisterScript("jForm_" + ContainerId, script, JavaScriptType.StartupScript);
            }
            else
            {
                this.Controls.AddLiteral(GetNoScriptForm());
            }
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (this.ClearResponse)
            {
                JFormResponse<T>.Current = null;
            }
        }
    }
}
